﻿using Common;
using Linguist.Dictionary;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;

namespace Linguist.Flat
{
    /// <summary>
    /// Represents a single state in an SentenceHMM
    /// </summary>
    public abstract class SentenceHMMState : ISerializable, ISearchState
    {
        private static int MASK_IS_FINAL = 0x1;
        private static int MASK_COLOR_RED = 0x2;
        private static int MASK_PROCESSED = 0x4;
        private static int MASK_FAN_IN = 0x8;
        private static int MASK_IS_WORD_START = 0x10;
        private static int MASK_IS_SHARED_STATE = 0x20;
        private static int MASK_WHICH = 0xffff;
        private static int SHIFT_WHICH = 0x8;

        /** A Color is used to tag SentenceHMM nodes */
        public enum Color { RED, GREEN }

        private static int globalStateNumber = -1000;

        private int stateNumber;

        // a number of separate variables are maintained in 'fields'
        // inorder to reduce the size of the SentenceHMM

        private int fields;
        private String name;

        public Dictionary<String, SentenceHMMStateArc> arcs;
        private SentenceHMMState parent;
        private String cachedName;
        private String fullName;
        public SentenceHMMStateArc[] successorArray;


        /**
        /// Creates a SentenceHMMState
         *
        /// @param name   the name of the current SentenceHMMState
        /// @param parent the parent of the current instance
        /// @param which  the index of the current instance
         */
        protected SentenceHMMState(String name, SentenceHMMState parent, int which) 
            :this()
        {
            this.name = name + which;
            this.parent = parent;
            setWhich(which);
            setProcessed(false);
            setColor(Color.RED);
        }


        /** Empty contructor */
        protected SentenceHMMState() 
        {
            stateNumber = globalStateNumber--;
            this.arcs = new Dictionary<String, SentenceHMMStateArc>();
        }


        /**
        /// Determines if this state marks the beginning of a word
         *
        /// @return true if the state marks the beginning of a word
         */
        public Boolean isWordStart() 
        {
            return (fields & MASK_IS_WORD_START) == MASK_IS_WORD_START;
        }


        /**
        /// Sets the 'wordStart' flag
         *
        /// @param wordStart <code>true</code> if this state marks the beginning of a word.
         */
        public void setWordStart(Boolean wordStart) 
        {
            if (wordStart) 
            {
                this.fields |= MASK_IS_WORD_START;
            } 
            else 
            {
                this.fields &= ~MASK_IS_WORD_START;
            }
        }


        /**
        /// Determines if this state is a shard state
         *
        /// @return true if the state marks the beginning of a word
         */
        public Boolean isSharedState() 
        {
            return (fields & MASK_IS_SHARED_STATE) == MASK_IS_SHARED_STATE;
        }


        /**
        /// Sets the shared state flag
         *
        /// @param shared <code>true</code> if this state is shared
         */
        public void setSharedState(Boolean shared) 
        {
            if (shared) {
                this.fields |= MASK_IS_SHARED_STATE;
            } else {
                this.fields &= ~MASK_IS_SHARED_STATE;
            }
        }


        /**
        /// Returns the word associated with the particular unit
         *
        /// @return the word associated with this state, or null if there is no word associated with this state.
         */
        public IWord getAssociatedWord() 
        {
            IWord word = null;
            SentenceHMMState state = this;

            while (state != null && !(state is WordState)) 
            {
                state = state.getParent();
            }

            if (state != null) 
            {
                WordState wordState = (WordState) state;
                word = wordState.getWord();
            }
            return word;
        }


        /**
        /// Retrieves a short label describing the type of this state. Typically, subclasses of SentenceHMMState will
        /// implement this method and return a short (5 chars or less) label
         *
        /// @return the short label.
         */
        public String getTypeLabel() 
        {
            return "state";
        }


        /**
        /// Determines if this state is a fan-in state. The search may need to adjust the pruning for states that fan in
        /// since they are shared by multiple paths
         *
        /// @return <code>true</code> if the state is a fan in state
         */
        public Boolean isFanIn() 
        {
            return (fields & MASK_FAN_IN) == MASK_FAN_IN;
        }


        /**
        /// Sets the fan in state
         *
        /// @param fanIn if true its a fan in state
         */
        public void setFanIn(Boolean fanIn) 
        {
            if (fanIn) {
                this.fields |= MASK_FAN_IN;
            } else {
                this.fields &= ~MASK_FAN_IN;
            }
        }


        /**
        /// Sets the processed flag for this state
         *
        /// @param processed the new setting for the processed flag
         */
        public void setProcessed(Boolean processed) 
        {
            if (processed) {
                this.fields |= MASK_PROCESSED;
            } else {
                this.fields &= ~MASK_PROCESSED;
            }
        }


        /**
        /// Determines if this state has been 'processed'. The meaning of 'processed' is not defined here, but is up to the
        /// higher levels
         *
        /// @return true if the state has been processed.
         */
        public Boolean isProcessed() 
        {
            return (fields & MASK_PROCESSED) == MASK_PROCESSED;
        }


        /** Reset process flags for this state and all successor states */
        public void resetAllProcessed() 
        {
            throw new NotImplementedException();
            //visitStates(
            //    new ISentenceHMMStateVisitor() 
            //{
            //    public Boolean visit(SentenceHMMState state) {
            //        state.setProcessed(false);
            //        return false;
            //    }
            //}, this, false);
        }


        /**
        /// Gets the word history for this state.
         *
        /// @return the word history.
         */
        //TODO Not implemented
        public WordSequence getWordHistory() 
        {
            return WordSequence.EMPTY;
        }


        /**
        /// Gets the number of successors
         *
        /// @return the number of successors
         */
        public int getNumSuccessors() 
        {
            return arcs.Count;
        }

        /**
        /// Returns the lextree state
         *
        /// @return the lex tree state
         */
        public SentenceHMMState getLexState() 
        {
            return this;
        }


        /**
        /// remove the given arc from the set of succors
         *
        /// @param arc the arc to remove
         */
        void deleteSuccessor(SentenceHMMStateArc arc) 
        {
            //arcs.Remove(arc);
            throw new NotImplementedException();
        }


        /**
        /// Connects the arc to this sentence hmm.  If the node at the end of the arc is already pointing to some other node
        /// as its predecessor, don't change that relationship, since its probably a result of the nodes being reused'
         *
        /// @param arc the path to the next state
         */
        public void connect(SentenceHMMStateArc arc) 
        {
            if (successorArray != null) {
                successorArray = null;
            }
            rawConnect(arc);
        }


        /**
        /// Connects the arc to this sentence hmm, but don't affect the predecessor relation ship
         *
        /// @param arc the arc to the next state
         */
        private void rawConnect(SentenceHMMStateArc arc) 
        {
            SentenceHMMState state = (SentenceHMMState) arc.getState();

            // attach the state-number because the state-signature is not necessarily unique
            String stateSign = state.getValueSignature() + state.getStateNumber();
            if (!arcs.ContainsKey(stateSign))
                arcs.Add(stateSign, arc);
            else
                arcs[stateSign] = arc;
        }


        /**
        /// Determines if this state is an emitting state
         *
        /// @return true if the state is an emitting state
         */
        public Boolean isEmitting() 
        {
            return false;
        }


        /**
        /// Determines if this is a final state
         *
        /// @return true if this is a final state
         */
        public Boolean isFinal() 
        {
            return (fields & MASK_IS_FINAL) == MASK_IS_FINAL;
        }


        /**
        /// Sets this is to be final state
         *
        /// @param state true if this is a final state
         */
        public void setFinalState(Boolean state) 
        {
            if (state) {
                this.fields |= MASK_IS_FINAL;
            } else {
                this.fields &= ~MASK_IS_FINAL;
            }
        }


        /**
        /// Determines if this state is a unit state
         *
        /// @return <code>true</code> if the state is a unit state.
         */
        public Boolean isUnit() 
        {
            return false;
        }


        /** Dumps this SentenceHMMState and all its successors. Just for debugging. */
        public void dumpAll() 
        {
            throw new NotImplementedException();
            //visitStates(new SentenceHMMStateVisitor() {
            //    public Boolean visit(SentenceHMMState state) {
            //        state.dump();
            //        return false;
            //    }
            //}, this, true);
        }


        /**
        /// Returns any annotation for this state
         *
        /// @return the annotation
         */
        protected String getAnnotation() 
        {
            return "";
        }


        /** Dumps this state */
        private void dump() 
        {
            throw new NotImplementedException();
            //System.out.println(" ----- " + getTitle() + " ---- ");
            //for (int i = 0; i < getSuccessors().length; i++) {
            //    SentenceHMMStateArc arc = (SentenceHMMStateArc) getSuccessors()[i];
            //    System.out.println("   -> " +
            //            arc.getState().toPrettyString());
            //}
        }


        /** Validates this SentenceHMMState and all successors */
        public void validateAll() 
        {
            // TODO fix me
        }


        /**
        /// Gets the name for this state
         *
        /// @return the name
         */
        public String getName() 
        {
            return name;
        }


        /**
        /// Returns a pretty name for this HMM
         *
        /// @return a pretty name
         */
        public String getPrettyName() 
        {
            return getName();
        }


        /** Returns the string representation of this object */
        override
        public String ToString() 
        {
            if (cachedName == null) {
                StringBuilder sb = new StringBuilder();
                if (isEmitting()) {
                    sb.Append('*');
                }
                sb.Append(getName());

                // String base = (isEmitting() ? "*" : "") + getName()
                //       + getWhich() + (isFinal() ? "!" : "");

                if (parent != null) {
                    sb.Append('_');
                    sb.Append(parent);
                }

                if (isFinal()) {
                    sb.Append('!');
                }
                cachedName = sb.ToString();
            }
            return cachedName;
        }


        /*
       /// Returns a pretty version of the string representation
       /// for this object
        *
       /// @return a pretty string
        */
        public String toPrettyString() 
        {
            return ToString();
        }


        /**
        /// Gets the fullName for this state
         *
        /// @return the full name for this state
         */
        public String getFullName() 
        {
            if (fullName == null) {
                if (parent == null) {
                    fullName = getName();
                } else {
                    fullName = getName() + '.' + parent.getFullName();
                }
            }

            return fullName;
        }


        /**
        /// Gets the signature for this state
         *
        /// @return the signature
         */
        public String getSignature() 
        {
            return getFullName();
        }


        /**
        /// gets the title (fullname + stateNumber) for this state
         *
        /// @return the title
         */
        public String getTitle() 
        {
            return getFullName() + ':' + stateNumber;
            // return getSignature() + ":" + stateNumber;
        }


        /**
        /// Retrieves the index for this state words
         *
        /// @return the index
         */
        public int getWhich() 
        {
            return (fields >> SHIFT_WHICH) & MASK_WHICH;
        }


        /**
        /// Sets the index for this state
         *
        /// @param which the index for this state
         */
        public void setWhich(int which) 
        {
            Trace.Assert(which >= 0 && which <= MASK_WHICH);
            fields |= (which & MASK_WHICH) << SHIFT_WHICH;
        }


        /**
        /// Retrieves the parent sate
         *
        /// @return the parent state (or null if this state does not have a parent state).
         */
        public SentenceHMMState getParent() 
        {
            return parent;
        }
    

        /**
        /// Searches the set of arcs for an arc that points to a state with an identical value
         *
        /// @param state the state to search for
        /// @return the arc or null if none could be found.
         */
        public SentenceHMMStateArc findArc(SentenceHMMState state) 
        {
            return arcs[state.getValueSignature()];
        }


        /**
        /// Returns the value signature of this unit
         *
        /// @return the value signature
         */
        public String getValueSignature() 
        {
            return getFullName();
        }


        /**
        /// Visit all of the states starting at start with the given vistor
         *
        /// @param visitor the state visitor
        /// @param start   the place to start the search
        /// @param sorted  if true, states are sorted before visited
        /// @return true if the visiting was terminated before all nodes were visited
         */
        public static Boolean visitStates(ISentenceHMMStateVisitor visitor,
                                          SentenceHMMState start, Boolean sorted) 
        {
            List<SentenceHMMState> states = collectStates(start);

            if (sorted) {
                // sort the states by stateNumber
                throw new NotImplementedException();
                //List<SentenceHMMState> sortedStates = new List<SentenceHMMState>(new Comparator<SentenceHMMState>() 
                //{

                //    public int compare(SentenceHMMState o1, SentenceHMMState o2) {
                //        SentenceHMMState so1 = o1;
                //        SentenceHMMState so2 = o2;
                //        return so1.stateNumber - so2.stateNumber;
                //    }
                //});

                //sortedStates.addAll(states);
                //states = sortedStates;
            }

            foreach (SentenceHMMState state in states) 
            {
                if (visitor.visit(state)) {
                    return true;
                }
            }
            return false;
        }


        /**
        /// Sets the color for this node
         *
        /// @param color the color of this node
         */
        public void setColor(Color color) 
        {
            if (color == Color.RED) {
                this.fields |= MASK_COLOR_RED;
            } else {
                this.fields &= ~MASK_COLOR_RED;
            }
        }


        /**
        /// Gets the color for this node
         *
        /// @return the color of this node
         */
        public Color getColor() {
            if ((fields & MASK_COLOR_RED) == MASK_COLOR_RED) {
                return Color.RED;
            } else {
                return Color.GREEN;
            }
        }


        /**
        /// Gets the state number for this state
         *
        /// @return the state number
         */
        private int getStateNumber() {
            return stateNumber;
        }


        /// <summary>
        /// Collect all states starting from the given start state 
        /// </summary>
        /// <param name="start">the state to start the search from</param>
        /// <returns>set of collected state</returns>
        public static List<SentenceHMMState> collectStates(SentenceHMMState start) 
        {
            List<SentenceHMMState> visitedStates = new List<SentenceHMMState>();
            List<SentenceHMMState> queue = new List<SentenceHMMState>();

            queue.Add(start);

            while (queue.Count!=0) 
            {
                SentenceHMMState state = queue[0];
                queue.RemoveAt(0);
                visitedStates.Add(state);
                ISearchStateArc[] successors = ((ISearchState)state).getSuccessors();
                foreach (ISearchStateArc arc in successors) 
                {
                    SentenceHMMState nextState = (SentenceHMMState)arc.getState();
                    if (!visitedStates.Contains(nextState) && !queue.Contains(nextState)) {
                        queue.Add(nextState);
                    }
                }
            }
            return visitedStates;
        }

        /**
        /// Returns the order of this particular state
         *
        /// @return the state order for this state
         */
        abstract public int getOrder();

        /// <summary>
        /// Gets a successor to this search state
        /// </summary>
        /// <returns>the set of successors</returns>
        ISearchStateArc[] ISearchState.getSuccessors()
        {
            if (successorArray == null)
            {
                successorArray = arcs.Values.ToArray();
            }
            return successorArray;
        }

        bool ISearchState.isEmitting()
        {
            return this.isEmitting();
        }

        string ISearchState.toPrettyString()
        {
            throw new NotImplementedException();
        }

        string ISearchState.getSignature()
        {
            throw new NotImplementedException();
        }

        object ISearchState.getLexState()
        {
            throw new NotImplementedException();
        }

        int ISearchState.getOrder()
        {
            throw new NotImplementedException();
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new NotImplementedException();
        }


        IWordSequence ISearchState.getWordHistory()
        {
            throw new NotImplementedException();
        }
    }
}
